昨天我們打好底了,有基本的容器,還有一些泛用的函式可以呼叫。今天來到實作第二天,讓我們來看看今天的需求:
劃掉的是昨天完成的需求,粗體的是今天的內容。
今天要來處理核心的生成→碰撞→合併的機制!
所以今天的需求依序有
第一個隨機初始圓形其實本來是要做轉換方向後生成的初始球要隨機生成 Level 1 或 Level 2的球,後來想想先留個空間,所以我們把 createBall 多丟一個 level,但先不改原本的初始與創建,同時當本來呼叫沒有給 level 的情況下,我們會預設給一個 1 當輸入。
function createBall(side,level)
{
if(level == null) level = 1;
.
.
.
var ball = Bodies.circle(x, y, ballInfo.size, options = { label: level, render : {fillStyle: ballInfo.color}, isSleeping : true}, 80);
Composite.add(engine.world, [ball]);
}
另外我們因為今天有了創建後的圖形初始要是睡眠狀態的,我們在這個創建球的函式加上 options : isSleeping : true,這樣我們透過這個方法創建的初始球就會是睡眠靜止的。
滑動後生成的部份我們先暫時設定成生成圖形在滑動的相反方向,也就是上滑的話,就把球創建在下且靜止,以此類推。因為我們昨天函式層級有拆乾淨,這邊的處理就很單純,只要針對傳入的方向再往裡面傳一層相反的方向就可以搞定。
// formObject.js
function generateBallAfterSwiped(side)
{
switch(side)
{
case "top":
createBall("down");
return;
case "down":
createBall("top");
return;
case "left":
createBall("right");
return;
case "right":
createBall("left");
return;
}
}
滑行後喚醒我們這邊用 forEach 的 syntax 來寫。記得我們的 Composite 單元嗎? engine.world 就是包含整個世界的組合體。組合體的內容有一個屬性是 bodies ,會列出該組合體中無論層級的所有物體,我們遍歷所有物體,目前我們沒有指派 engine 來管理 isSleeping 狀態,所以會控制 isSleeping 狀態的只有我們自己,而我們有控制讓 isSleeping 為 true 的時間點就只有當球被初始產生的時候。我們這邊就透過一個 if 判斷如果是睡眠的物體,我們就通通叫起來。
// swipedEvent.js
function swipeScreen(side)
{
.
.
.
engine.world.bodies.forEach(element => {
if(element.isSleeping) element.isSleeping = false;
});
.
}
最後是今天的重點,我們要來做球碰撞後的判定與融合。
首先我們在 engine 上掛上碰撞判定事件,函式的話我們拆到另一個檔案來處理。
我們的碰撞條件是兩顆一樣的球,碰撞後要做融合,那我們怎麼判斷兩顆球一樣呢?我們上面 createCircle 的地方其實偷偷加了一個屬性 label , label 掛上 level 的數值。所以我們只要判定兩個碰撞對象 label 一致,我們就要處理碰撞融合。這邊反過來寫,當碰撞的兩者 label 不同,我們就直接 return。
// main.js
.
Events.on(engine, "collisionStart", collisionTriggered);
.
// collisionHandle.js
function collisionTriggered(e)
{
if(e.name == 'collisionStart' && e.pairs.length > 0)
{
if(e.pairs[0].bodyA.label != e.pairs[0].bodyB.label) return;
ballCollision(e.pairs[0].bodyA.label, e.pairs[0].bodyA.id, e.pairs[0].bodyB.id);
}
}
接著是我們的碰撞融合判斷,我們會傳入兩個 body的 id,以及碰撞當下兩個球體的等級,拿取 id 的目的是我們要從 world 的組合體中的 bodies 用 id 拿出對應的物體。
新的等級就是舊的等級 + 1,拿到新的等級後再去拿對應的顏色與大小,新的位置是碰撞兩者的中點(計算我們抽到另一個 calculationHelper.js 來寫) ─ 最後我們再用這些資訊創建新的球體。
前面透過 id 拿到的物體,除了要他們的位置以外,也順便把他們從 world 移除,也就是在這個函式中我們完成了把碰撞的兩者清除,用兩者資訊創建一個新的球體加入世界這件事。
// formObject.js
function ballCollision(collisionLevel, bodyAId, bodyBId)
{
var newLevel = parseInt(collisionLevel) + 1;
var newBallInfo = getBallInfo(newLevel);
var bodyA = engine.world.bodies.filter(x=>x.id == bodyAId)[0];
var bodyB = engine.world.bodies.filter(x=>x.id == bodyBId)[0];
var newPosition = getMiddlePlace(bodyA.position, bodyB.position);
var ball = Bodies.circle(newPosition.x, newPosition.y, newBallInfo.size, options = { label: newLevel, render : {fillStyle: newBallInfo.color}}, 80);
Composite.add(engine.world, [ball]);
Composite.remove(engine.world, [bodyA,bodyB]);
}
到這裡今天的內容其實已經完成,但筆者在測試的時候有發現昨天加的那個滑鼠按鍵在測試中不太實際,會按到手很酸,所以最後再加上一個常見的鍵盤按鍵控制,不過這段就沒什麼特別的,一樣就是呼叫滑動的函式跟帶入方向。
// main.js
document.addEventListener('keydown', function(event) {
switch (event.key) {
case "ArrowLeft":
swipeScreen("left");
break;
case "ArrowRight":
swipeScreen("right");
break;
case "ArrowUp":
swipeScreen("up");
break;
case "ArrowDown":
swipeScreen("down");
break;
}
});
嘿!我們的 2048 已經有模有樣了!讓我們明天來幫它好好收尾,加上最後的一些內容,目前篇幅看起來是允許我們再加一些功能的個別實作,明天我們再來更新我們的需求清單,看看能不能多做些什麼讓呈現上更完美。